﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using Jarvis.Server.Workers.Messages;

namespace Jarvis.Server.Workers
{
    public abstract class Worker
    {
        private readonly ConcurrentQueue<Message> _messageQueue = new ConcurrentQueue<Message>();
        
        private static readonly ConcurrentDictionary<Type,ConcurrentBag<Worker>> Subscribers = new ConcurrentDictionary<Type, ConcurrentBag<Worker>>();

        private Thread _backingThread;
        private readonly object _sync = new object();
        private bool _stop = false;


        public string Name { get; set; }

        public void Start()
        {
            BeforeStart();
            lock (_sync)
            {
                _stop = false;
            }
            _backingThread = new Thread(() =>
            {
                while (true)
                {

                    lock (_sync)
                    {
                        if (_stop)
                        {
                            return;
                        }
                    }


                    Receive();
                    Loop();
                }
            });
            _backingThread.Name = Name ?? "Assign a name to this worker";
            _backingThread.Start();
        }

        public void Stop()
        {
            
            lock (_sync)
            {
                _stop = true;
            }

            _backingThread.Join();

            AfterStop();
        }

        private void Receive()
        {
            Message message;

            var result = _messageQueue.TryDequeue(out message);

            if(result)
            {
                OnMessageReceived(message);
            }
        }

        public void SendMessage(Message message)
        {
            foreach (var subscriber in Worker.SubscribersOf(message.GetType()))
            {
                subscriber._messageQueue.Enqueue(message);                
            }
        }

        protected static void Subscribe<TMessage>(Worker subscriber) where TMessage : Message
        {
            ConcurrentBag<Worker> subscribers;

            if(Subscribers.TryGetValue(typeof(TMessage), out subscribers))
            {
                subscribers.Add(subscriber);
            }else
            {
                Subscribers[typeof(TMessage)] = new ConcurrentBag<Worker>() { subscriber };
            }
        }

        protected static ConcurrentBag<Worker> SubscribersOf<TMessage>() where TMessage : Message
        {
            return SubscribersOf(typeof (TMessage));
        }

        private static ConcurrentBag<Worker> SubscribersOf(Type messageType)
        {
            ConcurrentBag<Worker> subscribers;

            if (!Subscribers.TryGetValue(messageType, out subscribers))
            {
                subscribers = new ConcurrentBag<Worker>();
            }

            return subscribers;
        }

        public abstract void Loop();

        public virtual void OnMessageReceived(Message message)
        {
            
        }

        protected virtual void AfterStop()
        {
            
        }

        protected virtual void BeforeStart()
        {
            
        }


    }
}
